package com.heima.article.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.article.config.CacheService;
import com.heima.article.mapper.ApArticleConfigMapper;
import com.heima.article.mapper.ApArticleContentMapper;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.mapper.ApAuthorMapper;
import com.heima.common.constants.app.ArticleConstants;
import com.heima.common.exception.config.CustException;
import com.heima.model.app.ArticleVisitStreamMess;
import com.heima.model.article.dtos.ApArticleDto;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.article.pojos.ApArticle;
import com.heima.article.service.ApArticleService;
import com.heima.model.article.pojos.ApArticleConfig;
import com.heima.model.article.pojos.ApArticleContent;
import com.heima.model.article.pojos.ApAuthor;
import com.heima.model.article.vos.HotArticleVo;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 文章信息表
 * </p>
 *
 * @author itheima
 */
@Slf4j
@Service
public class ApArticleServiceImpl extends ServiceImpl<ApArticleMapper, ApArticle> implements ApArticleService {
    @Autowired
    ApArticleMapper apArticleMapper;

    @Autowired
    ApAuthorMapper apAuthorMapper;
    @Autowired
    ApArticleConfigMapper apArticleConfigMapper;
    @Autowired
    ApArticleContentMapper apArticleContentMapper;
    @Autowired
    KafkaTemplate kafkaTemplate;
    @Autowired
    StringRedisTemplate redisTemplate;
    @Autowired
    CacheService cacheService;
    @Value("${file.oss.web-site}")
    private String website;

    /**
     * 保存ApArticle
     *
     * @param apArticleDto
     * @return
     */
    @Override
    public ResponseResult saveApArticle(ApArticleDto apArticleDto) {
        //1.参数校验
        if (apArticleDto == null) {
            CustException.cust(AppHttpCodeEnum.PARAM_INVALID, "无效参数");
        }
        //2.执行保存
        ApArticle apArticle = new ApArticle();
        BeanUtils.copyProperties(apArticleDto, apArticle);
        //文章作者id
        ApAuthor apAuthor = apAuthorMapper.selectOne(Wrappers.<ApAuthor>lambdaQuery().eq(ApAuthor::getName, apArticle.getAuthorName()));
        if (apAuthor == null) {
            CustException.cust(AppHttpCodeEnum.PARAM_INVALID, "作者不存在");
        }
        apArticle.setAuthorId(apAuthor.getId());
        //判断文章id是否存在，无保存，则更新
        if (apArticle.getId() == null) {
            save(apArticle);
        } else {
            //判断文章是否存在
            ApArticle article = getById(apArticle.getId());
            if (article == null) {
                CustException.cust(AppHttpCodeEnum.PARAM_INVALID, "文章已被删除");
            }
            // 根据文章的id删除ArticleContent和ArticleConfig表数据
            apArticleContentMapper.delete(Wrappers.<ApArticleContent>lambdaQuery()
                    .eq(ApArticleContent::getArticleId, apArticle.getId()));
            apArticleConfigMapper.delete(Wrappers.<ApArticleConfig>lambdaQuery()
                    .eq(ApArticleConfig::getArticleId, apArticle.getId()));

            updateById(apArticle);
        }
        //保存content和config表中相关的数据
        ApArticleConfig apArticleConfig = new ApArticleConfig();
        apArticleConfig.setArticleId(apArticle.getId());
        apArticleConfig.setIsComment(true);
        apArticleConfig.setIsForward(true);
        apArticleConfig.setIsDown(false);
        apArticleConfig.setIsDelete(false);
        apArticleConfigMapper.insert(apArticleConfig);
        ApArticleContent apArticleContent = new ApArticleContent();
        apArticleContent.setArticleId(apArticle.getId());
        apArticleContent.setContent(apArticleDto.getContent());
        apArticleContentMapper.insert(apArticleContent);
        //***********导入数据进es索引库
        Map<String, Object> map = new HashMap<>();
        map.put("id", apArticle.getId().toString());
        map.put("publishTime", apArticle.getPublishTime());
        map.put("layout", apArticle.getLayout());
        map.put("images", apArticle.getImages());
        map.put("authorId", apArticle.getAuthorId());
        map.put("title", apArticle.getTitle());
        kafkaTemplate.send(ArticleConstants.ARTICL_ES_SYNC_TOPIC, JSON.toJSONString(map));

        //返回结果（article_id）
        return ResponseResult.okResult(apArticle.getId());
    }

    /**
     * 加载文章列表
     *
     * @param type           1加载更多 2刷新
     * @param articleHomeDto
     * @return
     */
    @Override
    public ResponseResult load(Short type, ArticleHomeDto articleHomeDto) {
        //1.参数校验
        if (articleHomeDto == null) {
            articleHomeDto = new ArticleHomeDto();
        }
        //查询条件
        //size
        Integer size = articleHomeDto.getSize();
        if (size == null || size <= 0) {
            size = 30;
            articleHomeDto.setSize(size);
        }

        //time
        Date maxBehotTime = articleHomeDto.getMaxBehotTime();
        if (maxBehotTime == null) {
            articleHomeDto.setMaxBehotTime(new Date());
        }
        Date minBehotTime = articleHomeDto.getMinBehotTime();
        if (minBehotTime == null) {
            articleHomeDto.setMinBehotTime(new Date());
        }
        //频道
        if (StringUtils.isBlank(articleHomeDto.getTag())) {
            articleHomeDto.setTag(ArticleConstants.DEFAULT_TAG);
        }
        //type
        if (!type.equals(ArticleConstants.LOADTYPE_LOAD_MORE) && (!type.equals(ArticleConstants.LOADTYPE_LOAD_NEW))) {
            type = ArticleConstants.LOADTYPE_LOAD_MORE;
        }
        //3查询
        List<ApArticle> apArticles = apArticleMapper.loadArticleList(articleHomeDto, type);
        //4封装结果返回
        ResponseResult result = ResponseResult.okResult(apArticles);
        result.setHost(website);
        return result;
    }

    /**
     * 重新计算文章分值
     *
     * @param streamMess
     */
    @Override
    public void updateApArticle(ArticleVisitStreamMess streamMess) {
//1 查询文章数据
        if (streamMess.getArticleId() == null) {
            log.info("updateApArticle getArticleId is null");
            return;
        }
        ApArticle apArticle = apArticleMapper.selectById(streamMess.getArticleId());
        if (apArticle == null) {
            log.info("updateApArticle selectById is null id:{}", streamMess.getArticleId());
            return;
        }

        //2 计算分数（*3）
        int score = apArticlecomputeScore(apArticle);
        score = score * 3;


        //3 更新文章4大行为
        apArticle = handleBehaviorData(apArticle, streamMess);
        updateById(apArticle);


        //4 更新当前文章对应频道下的缓存
        String articleListStr = cacheService.get(ArticleConstants.HOT_ARTICLE_FIRST_PAGE + apArticle.getChannelId());
        updateArticleCache(apArticle, score, articleListStr,
                ArticleConstants.HOT_ARTICLE_FIRST_PAGE + apArticle.getChannelId());

        //5 更新推荐的缓存文章
        articleListStr = cacheService.get(ArticleConstants.HOT_ARTICLE_FIRST_PAGE + ArticleConstants.DEFAULT_TAG);
        updateArticleCache(apArticle, score, articleListStr,
                ArticleConstants.HOT_ARTICLE_FIRST_PAGE + ArticleConstants.DEFAULT_TAG);
    }

    /**
     *更新Redis缓存中的文章数据
     * @param apArticle
     * @param score
     * @param articleListStr
     * @param key
     */
    private void updateArticleCache(ApArticle apArticle, int score, String articleListStr, String key) {

        log.info("updateApArticle updateArticleCache apArticle:{},score:{}", apArticle, score);
        // 转集合 （30条 已排序）
        List<HotArticleVo> hotArticleVos = JSON.parseArray(articleListStr, HotArticleVo.class);

        if (hotArticleVos == null) {
            hotArticleVos = new ArrayList<>();
        }

        HotArticleVo hotArticleVo = new HotArticleVo();
        BeanUtils.copyProperties(apArticle, hotArticleVo);
        hotArticleVo.setScore(score);

        if (hotArticleVos.size() < 30) {
            hotArticleVos.add(hotArticleVo);
        } else {

            // 缓存中是否存在当前文章 存在则 更新分数
            boolean flag = false;
            for (HotArticleVo articleVo : hotArticleVos) {
                if (apArticle.getId().equals(articleVo.getId())) {
                    articleVo.setScore(articleVo.getScore() + score);
                    flag = true;
                    break;
                }
            }

            // 不存在 比较集合最后一条记录的分数
            if (!flag) {

                //*** 排序
                hotArticleVos = hotArticleVos.stream().sorted(Comparator.comparing(HotArticleVo::getScore).reversed()).collect(Collectors.toList());
                // 取最后一条
                HotArticleVo lastHotArticleVo = hotArticleVos.get(hotArticleVos.size() - 1);

                if (lastHotArticleVo.getScore() < score) { // 当前文章的分值大
                    // 删除集合最后一条
                    hotArticleVos.remove(lastHotArticleVo);

                    // 添加当前文章vo
                    hotArticleVos.add(hotArticleVo);
                }
            }

        }

        //**** 重新排序 ***
        hotArticleVos = hotArticleVos.stream()
                .sorted(Comparator.comparing(HotArticleVo::getScore).reversed())
                .limit(30)
                .collect(Collectors.toList());

        // 更新缓存
        cacheService.set(key, JSON.toJSONString(hotArticleVos));

        log.info("updateApArticle updateArticleCache success");
    }

    /**
     *更新文章的行为
     * @param apArticle
     * @param mess
     * @return
     */
    private ApArticle handleBehaviorData(ApArticle apArticle, ArticleVisitStreamMess mess) {

        // 阅读
        if (mess.getView() != 0) {
            int views = (int) (apArticle.getViews() == null ?
                    mess.getView() : apArticle.getViews() + mess.getView());
            apArticle.setViews(views);
        }

        //点赞
        if (mess.getLike() != 0) {
            int like = (int) (apArticle.getLikes() == null ?
                    mess.getLike() : mess.getLike() + apArticle.getLikes());
            apArticle.setLikes(like);
        }
        // 评论
        if (mess.getComment() != 0) {
            int comment = (int) (apArticle.getComment() == null ?
                    mess.getComment() : mess.getComment() + apArticle.getComment());
            apArticle.setComment(comment);
        }
        // 收藏
        if (mess.getCollect() != 0) {
            int collection = (int) (apArticle.getCollection() == null ?
                    mess.getCollect() : mess.getCollect() + apArticle.getCollection());
            apArticle.setCollection(collection);
        }
        return apArticle;
    }

    /**
     * 计算文章分数
     * @param apArticle
     * @return
     */
    private int apArticlecomputeScore(ApArticle apArticle) {

        int score = 0;
        //阅读
        if (apArticle.getViews() != null) {
            score += apArticle.getViews() * ArticleConstants.HOT_ARTICLE_VIEW_WEIGHT;
        }

        //点赞
        if (apArticle.getLikes() != null) {
            score += apArticle.getLikes() *ArticleConstants.HOT_ARTICLE_LIKE_WEIGHT;
        }

        // 评论 5
        if (apArticle.getComment() != null) {
            score += apArticle.getComment() * ArticleConstants.HOT_ARTICLE_COMMENT_WEIGHT;
        }
        // 收藏 8
        if (apArticle.getCollection() != null) {
            score += apArticle.getCollection() * ArticleConstants.HOT_ARTICLE_COLLECTION_WEIGHT;
        }

        return score;
    }
    /**
     * 根据参数加载文章列表  v2
     *
     * @param loadtype
     * @param dto
     * @param firstPage
     * @return
     */
    @Override
    public ResponseResult load2(Short loadtype, ArticleHomeDto dto, boolean firstPage) {

        // redis
        if (firstPage) {
            String listStr = cacheService.get(ArticleConstants.HOT_ARTICLE_FIRST_PAGE + dto.getTag());
            if (StringUtils.isNotBlank(listStr)) {
                List<HotArticleVo> articleVos = JSON.parseArray(listStr, HotArticleVo.class);
                return ResponseResult.okResult(articleVos);
            }
        }

        // 数据库
        return load(loadtype, dto);
    }

}
